iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 15
0

ToBoolean

常見的誤解

true 就是 1 ,false 就是 0,可是在 JS 中,number 就是 number,boolean 就是 boolean,你可以將兩者強制轉型,但那並不相同。

Falsy 值

JavaScript 所有值可以被分為兩類:

  1. 被強制轉型為布林會變為 false 的值。
  2. 其他所有值。

ToBoolean定義了一個抽象運算,說明強制轉型為布林會發生什麼事。

假值:

  • undefined
  • null
  • false
  • +0 / -0 / NaN
  • ""

JavaScript 沒有明確定義 truthy 的清單,語言規格暗示:沒有明確位於 falsy 清單的任何值都是 truthy。

Falsy 物件

document.all是一個類陣列,以前被 JS程式所使用,但因為早就被遺棄了,而因為許多網站仍需要仰賴此功能無法移除,就將此改為 falsy 物件。

除此之都是 Truthy ,連包含 false 的物件包裹器都是 Truthy。

Truthy 值

沒有明確位於 falsy 清單的任何值都是 truthy。

var a = "false";
var b = "0";
var c = "''";

var d = Boolean( a && b && c );

d;

因為這些變數內含 false 值,但變數本身就是 Truthy。

明確地強制轉型

可以明確看得出來的型別轉換。

明確地:Strings <——> Numbers

為了在 stringnumber 之間進行強制轉型,我們使用內建的String( ... )Number( ... ) 函式,要注意的是不使用 new 關鍵字,而我們要做的是在兩個型別做明確的強制轉型。換句話說,如果使用 new 關鍵字就是在建立物件包裹器。

var a = 42;
var b = String( a );

var c = "3.14";
var d = Number( c );

b; // "42"
d; // 3.14

無論是 String( ... ) 還是 Number( ... ),他們會把值強制轉型成基本型別值,詳細參考前面說的 ToNumberToString

除了String( ... )Number( ... )這兩個方法,還有其他方法可以在這兩個型別中做明確地轉換:

var a = 42;
var b = a.toString();
//表面上這看起來就是要轉成字串型別,
//但 toString 不能在基本型別值上被呼叫,所以他會被隱含的被封裝成物件包裹器。

var c = "3.14";
var d = +c;
// 這邊展示加號運算子的形式,
// 他不會做直接的加法運算或是字串連接
// 而是 + 把 c 強制轉型成數字
// 
// 如果你沒有用過這些方法的話,你可能認為這些是副作用。

b; // "42"
d; // 3.14

如果你喜歡+c模式的話,還是會有地方讓你困擾,像是:

var c = "3.14";
var d = 5+ +c;

d; // 8.14

-運算子也會做強制轉型,但它同時也會反轉數字的正負號,但你也不能做--,這樣會變成遞減運算子,所以你應該在他們之間放上空格- -,結果就會像- -"3.14"這樣,這樣就會做強制轉型。

所以如果與其他運算子緊密相鄰的話,你應該要避免用單元運算子來進行強制轉型。

日期轉為數字

單元 + 運算子另一個常見的用途是把 Date 物件強制轉型成數字:

var d = new Date( "Mon, 18 Aug 2014 08:53:06 CDT" );

+d; // 1408369986000

或是取得當下的時戳值:

var timestamp = +new Date();
var timestamp = +new Date;
//沒有參數的狀況下,小括號可加可不加
//但為了增加可讀性,請務必加小括號。

不過如果有不用強制轉型的方法應該優先使用,因為會更明確:

var timestamp = Date.now();

建議:不要用與日期有關的強制轉型,要產生現在時間戳值。
如果要取得非現在時間的時戳值,可用new Date( .. ).getTime()

~ (NOT)的奇幻旅程

很多人想避免用~ 運算子,但我們還是需要深入了解。

在前面有說 JS 的位元運算子只對 32 位元的作業有定義,所以我們的運算元必須要符合 32 位元值的表示法。這進行的規則是由 ToInt32 抽象運算來控制。

ToInt32 會先進行 ToNumber 的強制轉型,再套用 ToInt32 的規則。

在 ToInt32 執行過程並沒有強制轉型,位元運算子(|~)與某些特殊的 number 值並用,會產生類似強制轉型的效果,得出不同 number 值。

| 為位元 OR 運算子,先將兩數做二進制表示法,進行位元 OR 運算,再轉回 32 位元的數字。位元運算子也包含- + % / * >> <<
OR 運算:轉換為二進制,如果任何一個運算式的數字有 1,結果的那個數字就會有 1。否則,結果會在該數字出現 0。

以舉例 0 | x

0 | -0;		// 0
0 | NaN;	// 0
0 | Infinity;	// 0
0 | -Infinity;	// 0

NaNInfinity 這種特殊值無法以 32 位元表示,所以 ToInt32 指定 0 作為這些值轉換的結果。換句話說,無法轉成功 32 位元,就是 0 。

~ 運算子會先強制轉型為每個 32 位元的 number 值,再反轉每個位元。然而為何需要反轉?這起緣於離散數學,~ 進行的是二的補數。

我們能明白~x等於-(x+1)

~42;	// -(42+1) ==> -43

簡單的說 32 位元的有效範圍的 number 值的~運算,會為-1這個輸入值產生一個 falsy 的 0 值(-0),而其他值產生 truthy 的 number。

-1,常被稱為一個哨符值,已與相同型別的其他值做區隔。

indexOf(..)來說,如果有找到值,就回傳從零算起的索引位置,如果沒有找到索引位置就回傳-1,另外一個常見的狀況就是indexOf常做在一個布林的檢查,常判斷某個子字串是否有出現在另一個字串中:

var a = "Hello World";

if (a.indexOf( "lo" ) >= 0) {	// true
	// 找到了!
}
if (a.indexOf( "lo" ) != -1) {	// true
	// 找到了
}

if (a.indexOf( "ol" ) < 0) {	// true
	// 沒找到!
}
if (a.indexOf( "ol" ) == -1) {	// true
	// 沒找到!
}

像是>= 0== -1的寫法是一種容易洩露資訊的抽象層,我們可以用~搭配indexOf來改寫:

var a = "Hello World";

~a.indexOf( "lo" );            // -4   <-- truthy!

if (~a.indexOf( "lo" )) {	// true
	// 找到了!
}

~a.indexOf( "ol" );	        // 0    <-- falsy!
!~a.indexOf( "ol" );		// true

if (!~a.indexOf( "ol" )) {	// true
	// 沒找到!
	// 這個相等於 includes(..);
}

~ 接受indexOf的回傳值,對於-1,會得到 falsy 的 0,而其他值都是 truthy。
~>= 0== -1 的寫法比起來還是比較清楚明白。

截斷位元

~~ 截斷位元。

常用:多數開發者會用來截斷數字的小數部分。
誤解:多數人認為~~產生的結果與Math.floor(..)相同。
結果:就只是做 ToInt32 的強制轉型。

運作方式:

  • 第一個~先套用 ToInt32 的強制轉型,讓位元反轉
  • 第二個~進行另一次的逐位元反轉

注意:

  • ~~逐位元雙次反轉類似!!的行為。
  • 在 32 位元上才可能可靠運作。
  • 在負數上的運作與Math.floor(..)不同。

比較:~~xx | 0都可以把數字截斷成 32 位元的整數,但因為運算子的優先序,所以優先選用~~x,細節在第八章說明。

明確地:剖析數值字串

var a = "42";
var b = "42px";

Number( a );	// 42
parseInt( a );	// 42

Number( b );	// NaN
parseInt( b );	// 42

parseInt 剖析數字字串:

  • 傳值:字串。
  • 對於非數字字元:能夠容忍非數值字元。
  • 過程:在進行剖析時,遇到非數值字元就單純停下來。

Number 轉態字串為數字:

  • 傳值:數值字元字串。
  • 對於非數字字元:不容忍這種狀況,
  • 過程:在進行剖析時,遇到非數值字元轉換會失敗,產生 NaN 值。

兩者雖然相似,但有不同用途。
如果你不知道或是不在意其他非數值字元,就應該使用parseInt
如果能接受的值只有數字,就應該使用Number

注意:parseInt 的第二個引數是設定轉數字的數字進制。如果在舊型瀏覽器的parseInt傳入08的數值,在第二個引數沒有設置下,預設為八進制,最好永遠設置第二個引數是 10。

剖析非字串

parseInt( 1/0, 19 ); // 18
parseInt("Infinity", 19); //18

錯誤:傳入非字串給parseInt
base-19 的有效數值字元:0-9 與 a-i(不分大小)。
剖析過程:I 在 base-19 的值為 18,n不在有效數值字元中,剖析停止。

其他 parseInt 的怪異行為:

parseInt( 0.000008 );		// 0   ("0" from "0.000008")
parseInt( 0.0000008 );		// 8   ("8" from "8e-7")
parseInt( false, 16 );		// 250 ("fa" from "false")
parseInt( parseInt, 16 );	// 15  ("f" from "function..")

parseInt( "0x10" );			// 16
parseInt( "103", 2 );		// 2

明確地:* <——> Boolean

任何非布林值強制轉型為布林值,強制進行 ToBoolean 轉換的明確方法:

var a = "0";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g;

Boolean( a ); // true
Boolean( b ); // true
Boolean( c ); // true

Boolean( d ); // false
Boolean( e ); // false

Boolean( f ); // false
Boolean( g ); // false

Boolean(..) 並不常見也不慣用,原因是它也會反轉該值,將」 truthy 變成 falsy,因此最常用的是!!雙否定運算子。

var a = "0";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g;

!!a;	// true
!!b;	// true
!!c;	// true

!!d;	// false
!!e;	// false
!!f;	// false
!!g;	// false

如果沒有 Boolean(..)!! ,其他在 if(..) 述句中都是隱含的布林轉型,但這裡的目標是明確地轉型。

假設你希望在 JSON 序列化的過程中強制進行轉布林值,可以用下面方法:

var a = [
	1,
	function(){ /*..*/ },
	2,
	function(){ /*..*/ }
];

JSON.stringify( a ); // "[1,null,2,null]"

JSON.stringify( a, function(key,val){
	if (typeof val == "function") {
		// 強制函式進行 `ToBoolean` 轉換
		return !!val;
	}
	else {
		return val;
	}
} );
// "[1,true,2,true]"

或是

var a = 42;

var b = a ? true : false;

第二個方法,雖然看起來像是明確的強制轉型,但當中有個隱含的強制轉型,a 要先被強制轉型成布林值,才能進行真假值的測試,建議避免這種方法。


上一篇
Day14:YDKJS 第四次讀書會 筆記(上)
下一篇
Day16:圖解 HTTP Chapter07 確保 Web 安全的 HTTPS 筆記精要
系列文
寇丁人妻的前端書蟲日誌30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言